﻿using System.Windows;
using System.Windows.Media;
using WickedFlame.Core.State;

namespace WickedFlame.Core.Shapes
{
    public class CurveShape : VectorShape, IPolygonShape, IStateCapturable, ICopyable
    {
        public CurveShape()
            : this(0, 0, 1, 0)
        {
        }

        public CurveShape(Point start, Point end)
            : this(start.X, start.Y, end.X, end.Y)
        {
        }

        public CurveShape(double x1, double y1, double x2, double y2)
			: base()
		{
            StartPoint = new Point(x1, y1);
            CurvePoint = new Point(x1, y1);
            EndPoint = new Point(x2, y2);
        }

        public CurveShape(string source)
        {
            _definingGeometry = (PathGeometry)Geometry.Parse(source);

            StartPoint = _definingGeometry.Figures[0].StartPoint;
            EndPoint = ((QuadraticBezierSegment)_definingGeometry.Figures[0].Segments[0]).Point2;
            CurvePoint = ((QuadraticBezierSegment)_definingGeometry.Figures[0].Segments[0]).Point1;
        }

		#region Properties
		
		PathGeometry _definingGeometry;
        private PathGeometry DefiningGeometry
        {
            get
            {
                if (_definingGeometry == null)
                {
                    _definingGeometry = new PathGeometry();

                    QuadraticBezierSegment bezier = new QuadraticBezierSegment(
                        CurvePoint/*point 1 of curve*/,
                        //EndCurvePoint/*point 2 of curve*/,
                        EndPoint/*end of line*/,
                        true);

                    PathSegmentCollection segmentCollection = new PathSegmentCollection();
                    segmentCollection.Add(bezier);


                    PathFigure figure = new PathFigure(
                        StartPoint/*start of line*/,
                        segmentCollection,
                        false);

                    _definingGeometry.Figures.Add(figure);
                }

                _definingGeometry.Figures[0].StartPoint = StartPoint;
                ((QuadraticBezierSegment)_definingGeometry.Figures[0].Segments[0]).Point2 = EndPoint;
                ((QuadraticBezierSegment)_definingGeometry.Figures[0].Segments[0]).Point1 = CurvePoint;
                
                return _definingGeometry;
            }
        }

		public string GeometryString
        {
            get
            {
                return DefiningGeometry.ToString();
            }
        }

        Point _startPoint;
        public Point StartPoint
        {
            get
            {
                return _startPoint;
            }
            set
            {
                _startPoint = value;
                this.Invalidate();
            }
        }

        Point _endPoint;
        public Point EndPoint
        {
            get
            {
                return _endPoint;
            }
            set
            {
                _endPoint = value;
                this.Invalidate();
            }
        }

        Point _curvePoint;
        public Point CurvePoint
        {
            get
            {
                return _curvePoint;
            }
            set
            {
                _curvePoint = value;
                this.Invalidate();
            }
        }

		public int X1
		{
			get
			{
				return (int)StartPoint.X;
			}
			set
			{
				_startPoint.X = value;
				this.Invalidate();
			}
		}

		public int Y1
		{
			get
			{
				return (int)StartPoint.Y;
			}
			set
			{
				_startPoint.Y = value;
				this.Invalidate();
			}
		}

		public int X2
		{
			get
			{
				return (int)EndPoint.X;
			}
			set
			{
				_endPoint.X = value;
				this.Invalidate();
			}
		}

		public int Y2
		{
			get
			{
				return (int)EndPoint.Y;
			}
			set
			{
				_endPoint.Y = value;
				this.Invalidate();
			}
		}

		public int CurveX
		{
			get
			{
				return (int)CurvePoint.X;
			}
			set
			{
				_curvePoint.X = value;
				this.Invalidate();
			}
		}

		public int CurveY
		{
			get
			{
				return (int)CurvePoint.Y;
			}
			set
			{
				_curvePoint.Y = value;
				this.Invalidate();
			}
		}

        public override Rect Bounds
        {
            get
            {
                return DefiningGeometry.Bounds;
            }
        }

		public override int TrackerCount
		{
			get
			{
				return 3;
			}
		}

		#endregion

		#region Implementation

		public override void Invalidate()
		{
			base.Invalidate();

			NotifyPropertyChanged("StartPoint");
			NotifyPropertyChanged("EndPoint");
			NotifyPropertyChanged("X1");
			NotifyPropertyChanged("Y1");
			NotifyPropertyChanged("X2");
			NotifyPropertyChanged("Y2");
			NotifyPropertyChanged("CurveX");
			NotifyPropertyChanged("CurveY");

			DrawingCanvas.DrawTrackers();
		}

		public override void Draw(DrawingContext drawingContext)
        {
            drawingContext.DrawGeometry(Fill,
                Pen, //new Pen(Stroke, StrokeThickness),
                DefiningGeometry);
        }

        public override void DrawTracker(DrawingContext drawingContext, double scale)
        {
            Pen p = new Pen(Brushes.Blue, 0.5 / scale);

            drawingContext.DrawLine(p, StartPoint, CurvePoint);
            drawingContext.DrawLine(p, EndPoint, CurvePoint);

            p = new Pen(Brushes.Blue, 1 / scale);

            double r = 2.5 / scale;

            if (!(SelectedTracker == 1))
                drawingContext.DrawEllipse(Brushes.LightBlue, p, StartPoint, r, r);
            else
                drawingContext.DrawEllipse(Brushes.White, new Pen(Brushes.Black, 2 / scale), StartPoint, r, r);

            if (!(SelectedTracker == 2))
                drawingContext.DrawEllipse(Brushes.LightBlue, p, CurvePoint, r, r);
            else
                drawingContext.DrawEllipse(Brushes.White, new Pen(Brushes.Black, 2 / scale), CurvePoint, r, r);

            if (!(SelectedTracker == 3))
                drawingContext.DrawEllipse(Brushes.LightBlue, p, EndPoint, r, r);
            else
                drawingContext.DrawEllipse(Brushes.White, new Pen(Brushes.Black, 2 / scale), EndPoint, r, r);
        }

        public override Point GetTracker(int trackerNo)
        {
            if (trackerNo == 1)
                return StartPoint;
            else if (trackerNo == 2)
                return CurvePoint;
            else
                return EndPoint;
        }

        public override void SelectTracker(int tracker)
        {
            System.Diagnostics.Debug.WriteLine("Curve : " + tracker);
            //if (tracker == 1 || tracker == 3)
                base.SelectTracker(tracker);
        }

        //public override int HitTestInt(System.Windows.Point point)
        //{
        //    if (IsSelected)
        //    {
        //        for (int i = 1; i <= TrackerCount; i++)
        //        {
        //            if (GetTrackerRectangle(i).Contains(point))
        //                return i;
        //        }
        //    }

        //    if (PointInObject(point))
        //        return 0;

        //    return -1;
        //}

        protected override bool PointInObject(System.Windows.Point point)
        {
            return _definingGeometry.StrokeContains(Pen /*new Pen(this.Stroke, this.StrokeThickness)*/, point);
        }

        public override bool IntersectsWith(System.Windows.Rect rectangle)
        {
            var rectgeo = new RectangleGeometry(rectangle, 0, 0);
            var det = _definingGeometry.StrokeContainsWithDetail(Pen /*new Pen(this.Stroke, this.StrokeThickness)*/, rectgeo);

            if (det == IntersectionDetail.FullyContains || det == IntersectionDetail.FullyInside || det == IntersectionDetail.Intersects)
                return true;

            return false;
        }

        public override System.Windows.Input.Cursor GetTrackerCursor(int trackerNo)
        {
            switch (trackerNo)
            {
                case 1:
                case 2:
                case 3:
                    return System.Windows.Input.Cursors.SizeAll;
                default:
                    return System.Windows.Input.Cursors.Arrow;
            }
        }

        public override void MoveTracker(System.Windows.Point point, int trackerNo)
        {
            if (trackerNo == 1)
                StartPoint = point;
            else if (trackerNo == 2)
                CurvePoint = point;
            else
                EndPoint = point;

            this.Invalidate();

            CallChangedHandler();
        }

        public override void MoveTracker(int tracker, double deltaX, double deltaY)
        {
            if (tracker == 1)
                StartPoint = new Point(StartPoint.X + deltaX, StartPoint.Y + deltaY);
            else if (tracker == 2)
                CurvePoint = new Point(CurvePoint.X + deltaX, CurvePoint.Y + deltaY);
            else
                EndPoint = new Point(EndPoint.X + deltaX, EndPoint.Y + deltaY);

            this.Invalidate();

            CallChangedHandler();
        }

        public override void Move(double deltaX, double deltaY)
        {
            StartPoint = new Point(StartPoint.X + deltaX, StartPoint.Y + deltaY);
            CurvePoint = new Point(CurvePoint.X + deltaX, CurvePoint.Y + deltaY);
            EndPoint = new Point(EndPoint.X + deltaX, EndPoint.Y + deltaY);

            CallMovedHandler();
        }

		#endregion

		#region IPoligonShape Member

		public void AddShapeToPoligon(PolygonShape poligon)
        {
            //if (Distance(poligon.EndPoint, EndPoint) < Distance(poligon.EndPoint, StartPoint))
            if (SelectedTracker > 0)
            {
                // flip curve if the selected tracker is at the end
                if (SelectedTracker == 3)
                {
                    Point p1 = StartPoint;
                    Point p2 = EndPoint;

                    StartPoint = p2;
                    EndPoint = p1;
                }
            }
            else
                throw new ShapeException("No Tracker is isSelected");

            poligon.AddSegment(new QuadraticBezierSegment(
                ((QuadraticBezierSegment)_definingGeometry.Figures[0].Segments[0]).Point1,
                ((QuadraticBezierSegment)_definingGeometry.Figures[0].Segments[0]).Point2,
                ((QuadraticBezierSegment)_definingGeometry.Figures[0].Segments[0]).IsStroked),
                EndPoint);
        }

        public PolygonShape CreatePolygon()
        {
            // If tracker is selected it would mean that the shape will be combined with another
            // Flip shape so that the selected trackers will join
            if (SelectedTracker == 1)
            {
                Point p1 = StartPoint;
                Point p2 = EndPoint;

                StartPoint = p2;
                EndPoint = p1;
            }

            var polygon = new PolygonShape(StartPoint);
            polygon.AddSegment(new QuadraticBezierSegment(CurvePoint, EndPoint, true), EndPoint);

            polygon.Stroke = this.Stroke;
            polygon.StrokeThickness = this.StrokeThickness;
            polygon.Fill = this.Fill;

            return polygon;
        }

        #endregion

        #region IHistoryable Members

        public ShapeMemento CreateMementoState()
        {
            var memento = new CurveMemento();
            memento.SaveProperties(this);
            return memento;
        }

        public void ResetState(ShapeMemento memento)
        {
            memento.ResetProperties(this);
            this.Invalidate();
        }

        #endregion

        #region ICopyable Members

        public VectorShape Copy()
        {
            var shape = new CurveShape(StartPoint.X, StartPoint.Y, EndPoint.X, EndPoint.Y);
            shape.CurvePoint = this.CurvePoint;
            shape.Fill = this.Fill;
            shape.Stroke = this.Stroke;
            shape.StrokeThickness = this.StrokeThickness;

            return shape;
        }

        #endregion
    }
}
